package com.imis.demo.utils.uuid;
import java.security.SecureRandom;

/**
 * 自定义 ID 生成器 ID 生成规则: ID长达 64 bits<br/>
 * | 41 bits: Timestamp (毫秒) | 3 bits: 区域（机房） | 10 bits: 机器编号 | 10 bits: 序列号 |
 * @author wf.shu
 * @created 2017年6月19日 下午4:54:57
 */
public class DistributedUuid {
	/**
	 * 私有静态实例
	 */
	private static DistributedUuid instance;
	
	// 基准时间.Thu, 04 Nov 2010 01:42:54 GMT
	private long twepoch = 1288834974657L; 
	// 区域标志位数
	private final static long REGIONIDBITS = 3L;
	// 机器标识位数
	private final static long WORKERIDBITS = 10L;
	// 序列号识位数
	private final static long SEQUENCEBITS = 10L;

	// 区域标志ID最大值
	private final static long MAXREGIONID = -1L ^ (-1L << REGIONIDBITS);
	// 机器ID最大值
	private final static long MAXWORKERID = -1L ^ (-1L << WORKERIDBITS);
	// 序列号ID最大值
	private final static long SEQUENCEMASK = -1L ^ (-1L << SEQUENCEBITS);

	// 机器ID偏左移10位
	private final static long WORKERIDSHIFT = SEQUENCEBITS;
	// 业务ID偏左移20位
	private final static long REGIONIDSHIFT = SEQUENCEBITS + WORKERIDBITS;
	// 时间毫秒左移23位
	private final static long TIMESTAMPLEFTSHIFT = SEQUENCEBITS + WORKERIDBITS + REGIONIDBITS;

	private static long LASTTIMESTAMP = -1L;

	private long sequence = 0L;
	/** 机器编号 */
	private final long workerId;
	/** 区域ID */
	private final long regionId;
	
	/**
	 * 
	 * 构造函数
	 * <br>
	 *
	 * @author wf.shu
	 * @created 2017年6月19日 下午6:09:36
	 * @param workerId 机器编号
	 * @param regionId 区域ID 
	 */
	private DistributedUuid(long workerId, long regionId) {

		// 如果超出范围就抛出异常
		if (workerId > MAXWORKERID || workerId < 0) {
			throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
		}
		if (regionId > MAXREGIONID || regionId < 0) {
			throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0");
		}

		this.workerId = workerId;
		this.regionId = regionId;
	}
	
	private DistributedUuid(long workerId) {
		// 如果超出范围就抛出异常
		if (workerId > MAXWORKERID || workerId < 0) {
			throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
		}
		this.workerId = workerId;
		this.regionId = 0;
	}
	/**
	 * 
	 * <p>     
	 * 	 获取DistributedUuid实例【Singleton】
	 * </p>  
	 * @author  WeiFei.Shu
	 * @since 2018年7月19日 下午4:59:50
	 * @param workerId
	 * @param regionId
	 * @return DistributedUuid
	 */
	public static DistributedUuid getInstance(long workerId, long regionId){
		if(null==instance){
			synchronized (DistributedUuid.class) {
				if(null==instance){
					instance = new DistributedUuid(workerId,regionId);
				}
			}
		}
		return instance;
	}
	/**
	 * 
	 * <p>     
	 * 产生一个Id 
	 * </p>  
	 * @author  WeiFei.Shu
	 * @since 2018年7月19日 下午5:14:18
	 *    
	 * @return
	 * @return  long
	 */
	public long generate() {
		return this.nextId(false, 0);
	}

	/**
	 * 实际产生下个ID
	 * 
	 * @author wf.shu
	 * @created 2017年6月19日 下午4:58:57
	 * @param isPadding
	 * @param busId
	 * @return
	 */
	private synchronized long nextId(boolean isPadding, long busId) {

		long timestamp = timeGen();
		long paddingnum = regionId;

		if (isPadding) {
			paddingnum = busId;
		}
		//如果当前时间小于最后的时间时，抛出异常
		if (timestamp < LASTTIMESTAMP) {
			try {
				throw new Exception("Clock moved backwards.  Refusing to generate id for " + (LASTTIMESTAMP - timestamp) + " milliseconds");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		// 如果上次生成时间和当前时间相同,在同一毫秒内
		if (LASTTIMESTAMP == timestamp) {
			// sequence自增，因为sequence只有10bit，所以和sequenceMask相与一下，去掉高位
			sequence = (sequence + 1) & SEQUENCEMASK;
			// 判断是否溢出,也就是每毫秒内超过1024，当为1024时，与sequenceMask相与，sequence就等于0
			if (sequence == 0) {
				// 自旋等待到下一毫秒
				timestamp = tailNextMillis(LASTTIMESTAMP);
			}
		} else {
			// 如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加,
			// 为了保证尾数随机性更大一些,最后一位设置一个随机数
			sequence = new SecureRandom().nextInt(10);
		}

		LASTTIMESTAMP = timestamp;

		return ((timestamp - twepoch) << TIMESTAMPLEFTSHIFT) | (paddingnum << REGIONIDSHIFT) | (workerId << WORKERIDSHIFT) | sequence;
	}

	/**
	 * 防止产生的时间比之前的时间还要小（由于NTP回拨等问题）,保持增量的趋势.
	 * 
	 * @author wf.shu
	 * @created 2017年7月18日 下午1:09:52
	 * @param lastTimestamp
	 * @return
	 */
	private long tailNextMillis(final long lastTimestamp) {
		long timestamp = this.timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = this.timeGen();
		}
		return timestamp;
	}

	/**
	 * 获取当前的时间戳
	 * 
	 * @author wf.shu
	 * @created 2017年7月18日 下午1:10:02
	 * @return
	 */
	private long timeGen() {
		return System.currentTimeMillis();
	}
}